package actor

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"time"

	"github.com/AsynkronIT/protoactor-go/actor"
	"github.com/gin-gonic/gin"
)

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const DefaultHttpRequestMinDispatchInterval = 0 * time.Millisecond
const DefaultHttpRequestTimeout = 60 * time.Second

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type httpRequest struct {
	ctx *gin.Context
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type httpResponse struct {
	ctx *gin.Context
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func httpRequestResponse(pid *actor.PID, send *httpRequest, timeout time.Duration) (*httpResponse, error) {
	if pid == nil {
		return nil, fmt.Errorf("pid is nil")
	}
	f := RootContext.RequestFuture(pid, send, timeout)
	iRecv, err := f.Result()
	if err != nil {
		return nil, fmt.Errorf("RequestFuture fail, send=%#v, %w", send, err)
	}
	recv, ok := iRecv.(*httpResponse)
	if !ok {
		return nil, fmt.Errorf("recv %#v is not *response", recv)
	}
	return recv, nil
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type httpRequestHandler struct {
	gin.HandlerFunc
	*HttpRequestHandlerOption
	lastDispatchTime time.Time
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type HttpRequestHandlerOption struct {
	minDispatchInterval time.Duration
	timeout             time.Duration
	queued              bool
}

func (o HttpRequestHandlerOption) MinDispatchInterval() time.Duration {
	return o.minDispatchInterval
}

func (o HttpRequestHandlerOption) Timeout() time.Duration {
	return o.timeout
}

type HandlerOptionFunc func(option *HttpRequestHandlerOption)

func WithHttpRequestMinDispatchInterval(minDispatchInterval time.Duration) HandlerOptionFunc {
	return func(option *HttpRequestHandlerOption) {
		option.minDispatchInterval = minDispatchInterval
	}
}

func WithHttpRequestTimeout(timeout time.Duration) HandlerOptionFunc {
	return func(option *HttpRequestHandlerOption) {
		option.timeout = timeout
	}
}

func WithHttpRequestQueued(queued bool) HandlerOptionFunc {
	return func(option *HttpRequestHandlerOption) {
		option.queued = queued
	}
}

func NewHttpHandlerOption(optionFuncs ...HandlerOptionFunc) *HttpRequestHandlerOption {
	option := &HttpRequestHandlerOption{
		minDispatchInterval: DefaultHttpRequestMinDispatchInterval,
		timeout:             DefaultHttpRequestTimeout,
		queued:              true,
	}
	for _, f := range optionFuncs {
		f(option)
	}
	return option
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type httpServer struct {
	listener  net.Listener
	serverMux *gin.Engine
	server    *http.Server
	handlers  map[string]*httpRequestHandler
	actor     *Actor
}

func newHttpServer(actor *Actor) *httpServer {
	serverMux := gin.New()
	serverMux.Use(gin.Recovery())

	s := &httpServer{
		serverMux: serverMux,
		handlers:  map[string]*httpRequestHandler{},
		actor:     actor,
	}

	s.server = &http.Server{
		Handler: s.serverMux,
	}

	return s
}

func (s *httpServer) Listener() net.Listener {
	return s.listener
}

func (s *httpServer) Listen(addr string) (err error) {
	s.listener, err = net.Listen("tcp", addr)
	if err != nil {
		return
	}
	return
}

func (s *httpServer) Serve() {
	err := s.server.Serve(s.listener)
	if err != nil {
		log.Printf("http server Serve fail, %s", err)
		return
	}
}

func (s *httpServer) ServeTLS(certFile string, keyFile string) {
	err := s.server.ServeTLS(s.listener, certFile, keyFile)
	if err != nil {
		log.Printf("https server ServeTLS fail, %s", err)
		return
	}
}

func (s *httpServer) Register(method string, pattern string, fn gin.HandlerFunc, option *HttpRequestHandlerOption) (err error) {
	if _, ok := s.handlers[pattern]; ok {
		err = fmt.Errorf("http request handler already register, pattern=%s", pattern)
		return
	}

	if option == nil {
		option = NewHttpHandlerOption()
	}

	var h = &httpRequestHandler{
		HandlerFunc:              fn,
		HttpRequestHandlerOption: option,
	}

	s.handlers[pattern] = h

	if option.queued {
		s.serverMux.Handle(method, pattern, func(ctx *gin.Context) {
			_, err := httpRequestResponse(s.actor.PID(), &httpRequest{ctx: ctx}, h.timeout)
			if err != nil {
				s.actor.Error("httpRequestResponse fail, pattern=%s, %s", pattern, err)
				return
			}
		})
	} else {
		s.serverMux.Handle(method, pattern, fn)
	}

	return
}

func (s *httpServer) Dispatch(sender *actor.PID, request *httpRequest, ctx actor.Context) (err error) {
	pattern := request.ctx.FullPath()

	h, ok := s.handlers[pattern]
	if !ok {
		err = fmt.Errorf("recv unsupported http request, pattern=%s", pattern)
		return
	}

	send := &httpResponse{ctx: request.ctx}
	defer func() {
		if sender != nil {
			ctx.Send(sender, send)
		}
	}()

	now := time.Now()

	if h.minDispatchInterval > 0 {
		if now.Sub(h.lastDispatchTime) < h.minDispatchInterval {
			err = fmt.Errorf("http server request too ofter, pattern=%s", pattern)
			return
		}
	}

	h.lastDispatchTime = now

	h.HandlerFunc(request.ctx)

	return
}
